﻿using System;
using System.Collections.Generic;

using DDDSample.Common.Validate;
using DDDSample.Domain.Model.Handling;

namespace DDDSample.Domain.Model.Cargos
{
	/// <summary>
	/// An itinerary.
	/// </summary>
	public sealed class Itinerary : ValueObject<Itinerary>
	{
		private Cargo cargo;
		private IList<Leg> legs = new List<Leg>().AsReadOnly();

		public static readonly Itinerary EMPTY_ITINERARY = new Itinerary();

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="legs">List of legs for this itinerary.</param>
		public Itinerary(List<Leg> legs)
		{
			Validate.NotEmpty(legs);
			Validate.NoNullElements(legs);

			this.legs = legs.AsReadOnly();
		}

		/// <summary>
		/// the cargo that this itinerary is for
		/// 
		/// For maintaing referential integrity inside the Cargo aggregate,
		/// with internal visibility.
		/// 
		/// <see cref="Cargo.AttachItinerary(Itinerary)"/>
		/// <see cref="Cargo.detachItinerary()"/> 
		/// </summary>
		internal Cargo Cargo
		{
			set { this.cargo = value; }
		}

		/// <summary>
		/// the legs of this itinerary, as an readonly list.
		/// </summary>
		public IList<Leg> Legs
		{
			get { return this.legs; }
		}

		/// <summary>
		/// Test if the given handling event is expected when executing this itinerary.
		/// </summary>
		/// <param name="event">Event to test.</param>
		/// <returns>true if the event is expected</returns>
		public bool IsExpected(HandlingEvent @event)
		{
			if (Legs.Count == 0)
			{
				return true;
			}
			if (@event.Type == HandlingEvent.EventType.Receive)
			{
				// Check that the first leg's origin is the event's location
				Leg leg = Legs[0];
				return leg.From.Equals(@event.Location);
			}
			if (@event.Type == HandlingEvent.EventType.Load)
			{
				// Check that the there is one leg with same from location and carrier movement
				foreach (Leg leg in Legs)
				{
					if (leg.From.Equals(@event.Location) &&
						leg.CarrierMovement.Equals(@event.CarrierMovement))
						return true;
				}
				return false;
			}
			if (@event.Type == HandlingEvent.EventType.Unload)
			{
				// Check that the there is one leg with same to loc and carrier movement
				foreach (Leg leg in Legs)
				{
					if (leg.To.Equals(@event.Location) &&
						leg.CarrierMovement.Equals(@event.CarrierMovement))
						return true;
				}
				return false;
			}
			if (@event.Type == HandlingEvent.EventType.Claim)
			{
				// Check that the last leg's destination is from the event's location
				Leg leg = Legs[Legs.Count - 1];
				return leg.To.Equals(@event.Location);
			}

			// HandlingEvent.EventType.CUSTOMS;
			return true;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="other">itinerary to compare</param>
		/// <returns>true if the legs in this and the other itinerary are all equal.</returns>
		public override bool SameValueAs(Itinerary other)
		{
			if (other == null || this.legs.Count != other.legs.Count) return false;

			for (int i = 0; i < this.legs.Count; i++)
			{
				if (this.legs[i].Equals(other.legs[i]) == false) return false;
			}

			return true;
		}

		public override bool Equals(object obj)
		{
			if (this == obj) return true;
			if (obj == null || GetType() != obj.GetType()) return false;

			Itinerary other = (Itinerary)obj;

			return SameValueAs(other);
		}

		public override int GetHashCode()
		{
			if (Legs.Count == 0)
				return 0;
			if (Legs.Count == 1)
				return Legs[0].GetHashCode();

			int hashCode = 37 + Legs[0].GetHashCode();
			for (int i = 2; i < Legs.Count; i++)
				hashCode = hashCode * 17 + Legs[i].GetHashCode();

			return hashCode;
		}

		private Itinerary()
		{
			// Needed by Hibernate
		}

		// Auto-generated surrogate key
		private long id;
	}
}
